
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <sstream>

#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/logging/format/destination/rolling_file.hpp>
#include <boost/logging/format_fwd.hpp>

BOOST_LOG_FORMAT_MSG( optimize::cache_string_one_str<> )

#include <boost/logging/format_ts.hpp>
#include <boost/thread/xtime.hpp>
using namespace boost::logging;

using namespace boost::logging::scenario::usage;


#include "bocat/common/ini.hpp"
#include "bocat/common/logging.hpp"

namespace  bocat {
namespace  common {


struct	LogConfigGeneral
{
	std::string	path;

	bool	error_enabled;
	bool	info_enabled;
	bool	debug_enabled;

	int		rotate_files;
	int		rotate_size;

	#define	DEFAULT_ROTATE_FILES	4
	#define	DEFAULT_ROTATE_SIZE		64 * 1024 * 1024

	LogConfigGeneral()
		:	error_enabled(true), info_enabled(true), debug_enabled(false),
			rotate_files( DEFAULT_ROTATE_FILES ),
			rotate_size( DEFAULT_ROTATE_SIZE )
	{
	}
};

struct	LogConfigLevel
{
	std::string	filename;
	int		rotate_files;
	int		rotate_size;
	bool	enabled;

	LogConfigLevel()
		:	rotate_files( -1 ),
			rotate_size( -1 ),
			enabled( false )
	{
	}

};	//// struct LogConfigLevel


class	LogConfig
{
public	:
	LogConfig( std::string ini_filename, std::string section )
	{
		parse( ini_filename, section );
	}

public	:
	/*
	 *  [bocat::common::logging]
	 *  # GENERAL configure
	 *  log.path = /path/to/log/file
	 *  log.enable = all
	 *  #log.enable = error,info
	 *
	 *  # ERROR configure
	 *  log.error.enable = true
	 *  log.error.file.name = /path/to/error.log
	 *  log.error.file.rotate = 4
	 *  log.error.file.maxsize = 64 MB
	 *
	 *  # INFO configure
	 *  log.info.enable = true
	 *  log.info.file.name = /path/to/info.log
	 *  log.info.file.rotate = 4
	 *  log.info.file.maxsize = 64 MB
	 *
	 *  # DEBUG configure
	 *  log.debug.enable = true
	 *  log.debug.file.name = /path/to/debug.log
	 *  log.debug.file.rotate = 4
	 *  log.debug.file.maxsize = 64 MB
	 *
	 */
	void	parse( std::string ini_filename, std::string section )
	{
		INIFile	ini( ini_filename );

		// parse GENERAL options
		std::string	log_path = ini[ section ][ "log.path" ];
		if (log_path.size() > 1) {
			general.path = log_path;

			// make sure directroies exists
			// log-path is an log-file-prefix, it may be not a path
			//boost::filesystem::path	_log_path( log_path );
			//boost::filesystem::create_directories( _log_path );
		}
		std::string	log_enabled = ini[ section ][  "log.enable" ];
		if (log_enabled.size() > 1) {
			if ("all" == log_enabled) {
				general.error_enabled = true;
				general.info_enabled = true;
				general.debug_enabled = true;
			}
			else {
				std::vector< std::string >	fields;
				boost::split( fields, log_enabled, boost::is_any_of(","));
				for (size_t i=0; i < fields.size(); ++i) {
					std::string&	field = fields[ i ];
					boost::trim( field );
					if ( ("error" == field) || ("ERROR" == field) ) {
						general.error_enabled = true;
					}
					else if ( ("info" == field) || ("INFO" == field) ) {
						general.info_enabled = true;
					}
					else if ( ("debug" == field) || ("DEBUG" == field) ) {
						general.debug_enabled = true;
					}
				}
			}
		}

		/// TODO : to parse ERROR options

		/// TODO : to parse INFO options

		/// TODO : to parse DEBUG options

		// check valid configure
		if ( (general.path.size() < 1) && (error.filename.size() < 1) ) {
			std::ostringstream	msg;
			msg << "No error logging output directory defined in " << ini_filename;
			throw std::runtime_error( msg.str() );
		}

		// merge all options
		// merge ERROR with GENERAL options
		if ((general.error_enabled) || error.enabled) {
			if (!error.enabled) {
				error.filename = general.path + "error.log";
				error.rotate_files = general.rotate_files;
				error.rotate_size = general.rotate_size;
				error.enabled = true;
			}
		}
		// merge INFO with GENERAL options
		if ((general.info_enabled) || info.enabled) {
			if (!info.enabled) {
				info.filename = general.path + "info.log";
				info.rotate_files = general.rotate_files;
				info.rotate_size = general.rotate_size;
				info.enabled = true;
			}
		}
		// merge DEBUG with GENERAL options
		if ((general.debug_enabled) || debug.enabled) {
			if (!debug.enabled) {
				debug.filename = general.path + "debug.log";
				debug.rotate_files = general.rotate_files;
				debug.rotate_size = general.rotate_size;
				debug.enabled = true;
			}
		}
	}

private	:
	LogConfigGeneral	general;

public	:
	LogConfigLevel		error, info, debug;

};	//// class LogConfig


///////////////////////////////////////////////////////////////////////


typedef use<
        //  the filter is always accurate (but slow)
        ////filter_::change::always_accurate, 
        filter_::change::often< 60 >,  
        //  filter does not use levels
        filter_::level::no_levels, 
        // the logger is initialized once, when only one thread is running
        logger_::change::set_once_when_one_thread, 
        // the logger favors speed (on a dedicated thread)
        logger_::favor::speed> finder;

BOOST_DECLARE_LOG_FILTER(g_log_filter, finder::filter ) 
BOOST_DECLARE_LOG(g_log_err, finder::logger ) 
BOOST_DECLARE_LOG(g_log_info, finder::logger )
BOOST_DECLARE_LOG(g_log_dbg, finder::logger )

#define LDBG_ BOOST_LOG_USE_LOG_IF_FILTER(g_log_dbg(), g_log_filter()->is_enabled() ) 
#define LERR_ BOOST_LOG_USE_LOG_IF_FILTER(g_log_err(), g_log_filter()->is_enabled() )
#define LINF_ BOOST_LOG_USE_LOG_IF_FILTER(g_log_info(), g_log_filter()->is_enabled() ) 

BOOST_DEFINE_LOG_FILTER(g_log_filter, finder::filter ) 
BOOST_DEFINE_LOG(g_log_err, finder::logger )
BOOST_DEFINE_LOG(g_log_info, finder::logger )
BOOST_DEFINE_LOG(g_log_dbg, finder::logger )



class   BoostLogger  :   public Logger
{

public  :
    BoostLogger()
    {
        _error_msg_p = new char[ SIZE_MESSAGE_BUF ];
        _warn_msg_p = new char[ SIZE_MESSAGE_BUF ];
        _info_msg_p = new char[ SIZE_MESSAGE_BUF ];
        _debug_msg_p = new char[ SIZE_MESSAGE_BUF ];

        _configured = false;
    }

    virtual ~BoostLogger()
    {
		delete [] _error_msg_p;
		delete [] _warn_msg_p;
		delete [] _info_msg_p;
		delete [] _debug_msg_p;
    }

public  :
    virtual int error(int line_no, const char* file_name, const char* format, ...) 
    {
        // TODO : to mutex

        if (!_configured) {
            this->loadDefaultConfig();
        }

		if (!_error_enabled) {
			return	0;
		}

        va_list args;
        va_start(args, format);
        int length = vsnprintf(_error_msg_p, SIZE_MESSAGE_BUF - 1, format, args );
        _error_msg_p[ length ] = '\0';
        va_end(args);

        if (file_name) {
			const char*	file = basename( file_name );
            LERR_ << file << ":" << line_no << " " << _error_msg_p;
        }
        else {
            LERR_ << _error_msg_p;
        }

        return 0;
    }

    virtual int warn(int line_no, const char* file_name, const char* format, ...) 
    {
        // TODO : to mutex

        if (!_configured) {
            this->loadDefaultConfig();
        }

		if (!_error_enabled) {
			return	0;
		}

        va_list args;
        va_start(args, format);
        int length = vsnprintf(_warn_msg_p, SIZE_MESSAGE_BUF - 1, format, args );
        _warn_msg_p[ length ] = '\0';
        va_end(args);

        if (file_name) {
			const char*	file = basename( file_name );
            LERR_ << file << ":" << line_no << " " << _warn_msg_p;
        }
        else {
            LERR_ << _warn_msg_p;
        }

        return 0;
    }

    virtual int info(int line_no, const char* file_name, const char* format, ...) 
    {
        // TODO : to mutex

        if (!_configured) {
            this->loadDefaultConfig();
        }

		if (!_info_enabled) {
			return	0;
		}

        va_list args;
        va_start(args, format);
        int length = vsnprintf(_info_msg_p, SIZE_MESSAGE_BUF - 1, format, args );
        _info_msg_p[ length ] = '\0';
        va_end(args);

        if (file_name) {
			const char*	file = basename( file_name );
            LINF_ << file << ":" << line_no << " " << _info_msg_p;
        }
        else {
            LINF_ << _info_msg_p;
        }

        return 0;
    }

    virtual int debug(int line_no, const char* file_name, const char* format, ...) 
    {
        // TODO : to mutex

        if (!_configured) {
            this->loadDefaultConfig();
        }

		if (!_debug_enabled) {
			return	0;
		}

        va_list args;
        va_start(args, format);
        int length = vsnprintf(_debug_msg_p, SIZE_MESSAGE_BUF - 1, format, args );
        _debug_msg_p[ length ] = '\0';
        va_end(args);

        if (file_name) {
			const char*	file = basename( file_name );
            LDBG_ << file << ":" << line_no << " " << _debug_msg_p;
        }
        else {
            LDBG_ << _debug_msg_p;
        }

        return 0;
    }

private	:
	const char*	basename(const char* file_name)
	{
		const char*	slash = strrchr(file_name, '/');
		if (slash == NULL) {
			return file_name;
		}
		else {
			return slash + 1;
		}
	}

private :
    void    loadDefaultConfig()
    {
        if (_configured) {
            return;
        }

        // Err log
        //g_log_err()->writer().add_formatter( formatter::idx(), "[%] "  );
        //g_log_err()->writer().add_formatter( formatter::thread_id(), "T% "  );
        g_log_err()->writer().add_formatter( formatter::time("$yyyy-$MM-$dd $hh:$mm:$ss.$mili ") );
        g_log_err()->writer().add_formatter( formatter::append_newline() );
        g_log_err()->writer().add_destination( destination::cerr() );
		_error_enabled = true;

        // App log
        //g_log_info()->writer().add_formatter( formatter::thread_id(), "T% "  );
        g_log_info()->writer().add_formatter( formatter::time("$yyyy-$MM-$dd $hh:$mm:$ss.$mili ") );
        g_log_info()->writer().add_formatter( formatter::append_newline() );
        g_log_info()->writer().add_destination( destination::cout() );
		_info_enabled = true;

        // Debug log
        //g_log_dbg()->writer().add_formatter( formatter::thread_id(), "T% "  );
        g_log_dbg()->writer().add_formatter( formatter::time("$yyyy-$MM-$dd $hh:$mm:$ss.$mili ") );
        g_log_dbg()->writer().add_formatter( formatter::append_newline() );
        g_log_dbg()->writer().add_destination( destination::cout() );
		_debug_enabled = true;

        g_log_info()->mark_as_initialized();
        g_log_err()->mark_as_initialized();
        g_log_dbg()->mark_as_initialized();
        
        _configured = true;
    }

public  :
    
    void    configure(const char* config_file, const char* section)
    {
		LogConfig	config( config_file, section );

        // Err log
		if ( config.error.enabled ) {
			//g_log_err()->writer().add_formatter( formatter::idx(), "[%] "  );
			//g_log_err()->writer().add_formatter( formatter::thread_id(), "T% "  );
			g_log_err()->writer().add_formatter( formatter::time("$yyyy-$MM-$dd $hh:$mm:$ss.$mili ") );
			g_log_err()->writer().add_formatter( formatter::append_newline() );
			//g_log_err()->writer().add_destination( destination::file("err03.txt") );
			g_log_err()->writer().add_destination( destination::rolling_file(config.error.filename,
                                                    destination::rolling_file_settings()
                                                        .initial_erase(false)
                                                        .max_size_bytes( config.error.rotate_size )
                                                        .file_count( config.error.rotate_files )
                                                        .flush_each_time(true)
                                                        .extra_flags(std::ios_base::binary)
                                                    ));

			_error_enabled = true;
		}

        // App log
		if ( config.info.enabled ) {
			//g_log_info()->writer().add_formatter( formatter::thread_id(), "T% "  );
			g_log_info()->writer().add_formatter( formatter::time("$yyyy-$MM-$dd $hh:$mm:$ss.$mili ") );
			g_log_info()->writer().add_formatter( formatter::append_newline() );
			g_log_info()->writer().add_destination( destination::rolling_file(config.info.filename,
                                                    destination::rolling_file_settings()
                                                        .initial_erase(false)
                                                        .max_size_bytes( config.info.rotate_size )
                                                        .file_count( config.info.rotate_files )
                                                        .flush_each_time(true)
                                                        .extra_flags(std::ios_base::binary)
                                                    ));

			_info_enabled = true;
		}

        // Debug log
		if ( config.debug.enabled ) {
			g_log_dbg()->writer().add_formatter( formatter::time("$yyyy-$MM-$dd $hh:$mm:$ss.$mili ") );
			g_log_dbg()->writer().add_formatter( formatter::append_newline() );
			g_log_dbg()->writer().add_destination( destination::rolling_file(config.debug.filename,
                                                    destination::rolling_file_settings()
                                                        .initial_erase(false)
                                                        .max_size_bytes( config.debug.rotate_size )
                                                        .file_count( config.debug.rotate_files )
                                                        .flush_each_time(true)
                                                        .extra_flags(std::ios_base::binary)
                                                    ));

			_debug_enabled = true;
		}

        g_log_info()->mark_as_initialized();
        g_log_err()->mark_as_initialized();
        g_log_dbg()->mark_as_initialized();
        
        _configured = true;
    }


	bool	errorEnabled(void) { return _error_enabled; }
	bool	infoEnabled(void)  { return _info_enabled; }
	bool	debugEnabled(void) { return _debug_enabled; }

private :  
    
    enum {
        SIZE_MESSAGE_BUF = 2048,
    };

    char*   _error_msg_p;
    char*   _warn_msg_p;
    char*   _info_msg_p;
    char*   _debug_msg_p;

    bool    _configured;

	bool	_error_enabled;
	bool	_info_enabled;
	bool	_debug_enabled;
};

///////////////////////////////////////////////////////////////////////
//

static  BoostLogger sg_logger;

Logger* getLogger(const char* category)
{
    return &sg_logger;
}


}   // namespace common
}   // namespace bocat

